#include <stdbool.h>
#include <stdio.h>
#include "clib_random_uuid.h"
#include "../num/clib_random_clock.h"

static clib_uint64_t clib_random_uuid_uuid4_xorshift128plus(clib_uint64_t *s) {
    clib_uint64_t s1 = s[0];
    clib_uint64_t const s0 = s[1];
    s[0] = s0;
    s1 ^= s1 << 23;
    s[1] = s1 ^ s0 ^ (s1 >> 18) ^ (s0 >> 5);
    return s[1] + s0;
}

static void clib_random_uuid_uuid4_generate(clib_uint8_t uuid[16], clib_uint64_t seed[2]) {
    union {
        clib_uint8_t b[16];
        clib_uint64_t word[2];
    } s;
    s.word[0] = clib_random_uuid_uuid4_xorshift128plus(seed);
    s.word[1] = clib_random_uuid_uuid4_xorshift128plus(seed);
    clib_int32_t i = 0;
    clib_int32_t n = 0;
    clib_int32_t t = 0;
    clib_int32_t c1 = 0;
    clib_int32_t c2 = 0;
    for (t = 0; t < 32; t++) {
        n = s.b[i >> 1];
        n = (i & 1) ? (n >> 4) : (n & 0xf);
        if (t == 16) // y
        {
            c2 = (n & 0x3) + 8;
            i++;
        } else if (t == 12) c2 = 4; // 4
        else // x
        {
            c2 = n;
            i++;
        }
        if (t & 1) uuid[t >> 1] = (clib_uint8_t) (c1 * 16 + c2);
        c1 = c2;
    }
}

int clib_random_uuid(clib_uint8_t uuid[CLIB_RANDOM_UUID_LENGTH]) {
    clib_check_if_true_return_value(uuid == NULL, CLIB_RES_PARAM_ERROR);
    clib_uint64_t seed[2];
    seed[0] = clib_random_clock();
    seed[1] = clib_random_clock();
    clib_random_uuid_uuid4_generate(uuid, seed);
    return CLIB_RES_OK;
}


int clib_random_uuid_to_string(char uuid[CLIB_RANDOM_UUID_STRING_LENGTH]) {
    clib_check_if_true_return_value(uuid == NULL, CLIB_RES_PARAM_ERROR);
    clib_uint8_t result_uuid[CLIB_RANDOM_UUID_LENGTH];
    if (CLIB_RES_OK != clib_random_uuid(result_uuid)) {
        return CLIB_RES_OTHER_ERROR;
    }
    sprintf(uuid, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", result_uuid[0], result_uuid[1], result_uuid[2],
            result_uuid[3], result_uuid[4], result_uuid[5], result_uuid[6], result_uuid[7], result_uuid[8], result_uuid[9], result_uuid[10], result_uuid[11], result_uuid[12], result_uuid[13],
            result_uuid[14], result_uuid[15]);
    return CLIB_RES_OK;
}
